import { isSameVnode } from "./vnode"

export function createElement (vnode) {
  const { tag, data, children ,text } = vnode
  if (typeof tag === 'string') {
    vnode.el = document.createElement(tag) // 虚拟dom上添加el属性，保存对应的真实节点
    updateProps(vnode.el, {}, data)

    children.map(c => {
      vnode.el.appendChild(createElement(c))
    })
  } else {
    vnode.el = document.createTextNode(text)
  }

  return vnode.el
}

function updateProps (el, oldProps, props) {
  // 老的属性中有，新的没有，要删除老的
  let oldStyles = oldProps.style || {}
  let newStyles = props.style || {}
  for (const key in oldStyles) { // 老的样式中有，新的没有，删除老的
    if (!newStyles[key]) {
      el.style[key] = ''
    }
  }

  for (const key in oldProps) { // 老的属性中，新的没有，删除老的
    if (!props[key]) {
      el.removeAttribute(key)
    }
  }

  for (const key in props) { // 用新的覆盖老的
    if (key === 'style') {
      for (const sKey in props[key]) {
        el.style[sKey] = props[key][sKey]
      }
    } else if (key === 'class') {
      el.className = props[key]
    } else {
      el.setAttribute(key, props[key])
    }
  }
}

export function patch (oldVNode ,vnode) {
  const isRealElement = oldVNode.nodeType
  if (isRealElement) {
    const el = createElement(vnode)
    const parentElement = oldVNode.parentNode
    parentElement.insertBefore(el, el.nextSibling)
    parentElement.removeChild(oldVNode)
  
    return el
  } else {
    // 1. 两个节点不是同一个节点，直接删除老的换上新的
    // 2. 两个节点是同一个节点（判断节点的 tag 和 节点的 key）比较两个节点属性是否有差异（复用老的节点，将差异的属性更新）
    // 3. 节点比较完毕后就需要比较两人的儿子

    return patchVnode(oldVNode, vnode)
  }
}

function patchVnode (oldVNode, vnode) {
  // 不相同，替换
  if (!isSameVnode(oldVNode, vnode)) {
    // 老节点的父亲 进行替换
    let el = createElement(vnode)
    oldVNode.el.parentNode.replaceChild(el, oldVNode.el)
    return el
  }

  // 文本的情况 比较文本内容
  let el = vnode.el = oldVNode.el // 复用老节点的元素
  if (!oldVNode.tag) {
    if (oldVNode.text !== vnode.text) {
      el.textContent = vnode.text
    }
  }

  // 标签 比对标签的属性
  updateProps(el, oldVNode.data, vnode.data)

  // 比较儿子节点
  // 1. 一方有儿子 一方没有
  // 2. 两方都有儿子
  let oldChildren = oldVNode.children || []
  let newChildren = vnode.children || []

  if (oldChildren.length > 0 && newChildren.length > 0) {
    // 完整的diff算法 需要表两个人的儿子
    updaeChildren(el, oldChildren, newChildren)
  } else if (newChildren.length > 0) { // 没有老的，有新的
    mountChildren(el, newChildren)
  } else if (oldChildren.length > 0) { // 新的没有，老的有 删除
    el.innerHTML = '' // 可以循环删除
  }

  return el
}

function mountChildren (el, newChildren) {
  for (let i = 0; i < newChildren.length; i++) {
    const child = newChildren[i]
    el.appendChild(createElement(child))
  }
}

// 优化策略
// vue2 采用双指针的方式 比较两个节点
function updaeChildren (el, oldChildren, newChildren) {
  // 四个指针
  let oldStartIndex = 0
  let newStartIndex = 0
  let oldEndIndex = oldChildren.length - 1
  let newEndIndex = newChildren.length - 1

  // 四个节点
  let oldStartVnode = oldChildren[0]
  let newStartVnode = newChildren[0]
  let oldEndVnode = oldChildren[oldEndIndex]
  let newEndVnode = newChildren[newEndIndex]

  function makeIndexByKey (children) {
    let map = {}
    children.forEach((child, index) => {
      map[child.key] = index
    })
    return map
  }
  let map = makeIndexByKey(oldChildren)

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    // 双方有一方头指针，大于尾部指针则停止循环

    if (!oldStartVnode) {
      oldStartVnode = oldChildren[++oldStartIndex]
    } else if (!oldEndVnode) {
      oldEndVnode = oldChildren[--oldEndIndex]
    } else if (isSameVnode(oldStartVnode, newStartVnode)) { // 头头比对
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldChildren[++oldStartIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else if (isSameVnode(oldEndVnode, newEndVnode)) { // 尾尾比对
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldChildren[--oldEndIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else if (isSameVnode(oldEndVnode, newStartVnode)) { // 尾头比对
      patchVnode(oldEndVnode, newStartVnode)
      el.insertBefore(oldEndVnode.el, oldStartVnode.el) // 将老的最后一项移动到最前面
      oldEndVnode = oldChildren[--oldEndIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else if (isSameVnode(oldStartVnode, newEndVnode)) { // 头尾比对
      patchVnode(oldStartVnode, newEndVnode)
      el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling) // 将老的开始移动到最后的后面
      oldStartVnode = oldChildren[++oldStartIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else { // 乱序比对
      // 根据老的列表做一个映射关系，用新的去找，找到则移动，找不到则添加，最后多余的删除
      let moveIndex = map[newStartVnode.key] // 如果拿到则说明是要移动的索引
      if (moveIndex !== undefined) {
        let moveVnode = oldChildren[moveIndex] // 找到对应的虚拟节点
        el.insertBefore(moveVnode.el, oldStartVnode.el)
        oldChildren[moveIndex] = undefined // 表示这个节点已经移动走了
        patchVnode(moveVnode, newStartVnode) // 比对属性和子节点
      } else {
        el.insertBefore(createElement(newStartVnode), oldStartVnode.el)
      }

      newStartVnode = newChildren[++newStartIndex]
    }
  }

  if (newStartIndex <= newEndIndex) { // 新节点没有处理完，追加剩余的
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      const childEl = createElement(newChildren[i])

      // 插入的话，可能是 后面插入(头头比对) 也可能是 前面插入(尾尾比对)
      // 当前的标识
      const anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null

      el.insertBefore(childEl, anchor) // anchor 为null, 表示 appendChild
    }
  } else { // 旧节点没有处理完，则删除
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      if (oldChildren[i].el) {
        const childEl = oldChildren[i].el
        el.removeChild(childEl)
      }
    }
  }
}